/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package fr.inria.openmath.omapi.implementation;

/**
 *
 * @author Mr.Sam
 */
import java.io.*;
import java.math.BigInteger;
import fr.inria.openmath.omapi.*;


/**
 * This class implements the Printer interface with a human readable form.
 * OpenMath objects are output in a less verbose way than in XML encoding
 * and of course in a more readable form than in Binary enconding.
 * This printer is thus intended to help humans wanting to read OpenMath
 * objects.
 */
public class HumanPrinter implements StreamPrinter {

    OutputStream output;

    int indent;

    int curColumn;


    private void indent() throws IOException {
        int c;

        if (indent != 0) {
            output.write((byte) '\n');
            for (c = 0; c < curColumn; c++) {
                output.write((byte) ' ');
            }
        }
    }

    private void indent(int incr) throws IOException {
        if (incr >= 0) {
            indent();
            curColumn += indent * incr;
        } else {
            curColumn += indent * incr;
            indent();
        }
    }

    private void indentIncr(int incr) {
        curColumn += indent * incr;
    }

    private String printLongHexInvertEndianess(long l)
            throws NumberFormatException {
        char enc[] = new char[16], LSNib, MSNib;
        int i;

        for (i = 0; i < 8; i++) {
            // OPTIM precomputed array for encoding
            MSNib = (char) ((l & 0xf0) >> 4);
            LSNib = (char) (l & 0x0f);
            l = l >> 8;
            if (MSNib < (char) 10)
                enc[i * 2] = (char) (MSNib + '0');
            else
                enc[i * 2] = (char) (MSNib + 'a' - 10);
            if (LSNib < (char) 10)
                enc[i * 2 + 1] = (char) (LSNib + '0');
            else
                enc[i * 2 + 1] = (char) (LSNib + 'a' - 10);
        }
        return new String(enc);
    }


    public HumanPrinter (OutputStream output, int indent) {
        this.output = output;
        this.indent = indent;
    }

    public HumanPrinter (OutputStream output) {
        this(output, 2);
    }

    public HumanPrinter () {
        this(new BufferedOutputStream(new DataOutputStream(System.out)), 2);
    }


    /**
     * Set the number of spaces skiped at each indentation level.
     *
     * @param indent The number of spaces skiped at each indentation level.
     *   0 means no indent will be performed.
     *
     */
    public void setIndent(int indent) {
        this.indent = indent;
    }

    /**
     * Get the curent number of spaces skiped at each indentation level.
     *
     * @return The curent number of spaces skiped at each indentation level.
     */
    public int getIndent() {
        return this.indent;
    }


    /**
     * @see fr.inria.openmath.omapi.StreamPrinter#setOutput
     */
    public void setOutput(OutputStream output) throws OMException {
        this.output = output;
    }

    /**
     * @see fr.inria.openmath.omapi.StreamPrinter#getOutput
     */
    public OutputStream getOutput() {
        return this.output;
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putBeginObject
     */
    public void putBeginObject() throws IOException {
        indent(+1);
        output.write("Object".getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putEndObject
     */
    public void putEndObject() throws IOException {
        indent(-1);
        output.write("/Object".getBytes());
        output.flush();
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putBeginApplication
     */
    public void putBeginApplication() throws IOException {
        indent(+1);
        output.write("Application".getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putEndApplication
     */
    public void putEndApplication() throws IOException {
        indent(-1);
        output.write("/Application".getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putBeginAttribution
     */
    public void putBeginAttribution() throws IOException {
        indent(+1);
        output.write("Attribution".getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putEndAttribution
     */
    public void putEndAttribution() throws IOException {
        indent(-2);
        output.write("/Attribution".getBytes());
    }


    /**
     * @see fr.inria.openmath.omapi.Printer#putBeginAttributePairs
     */
    public void putBeginAttributePairs() throws IOException {
        indent(1);
        output.write("Attributes:".getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putEndAttributePairs
     */
    public void putEndAttributePairs() throws IOException {
        indent(-1);
        output.write("Value:".getBytes());
        indentIncr(+1);
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putBeginBind
     */
    public void putBeginBind() throws IOException {
        indent(+1);
        output.write("Bind".getBytes());
        indent(+1);
        output.write("Binder:".getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putEndBind
     */
    public void putEndBind() throws IOException {
        indent(-2);
        output.write("/Bind".getBytes());
    }


    /**
     * @see fr.inria.openmath.omapi.Printer#putBeginBVars
     */
    public void putBeginBVars() throws IOException {
        indent(-1);
        output.write("Bound variables:".getBytes());
        indentIncr(+1);
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putEndBVars
     */
    public void putEndBVars() throws IOException {
        indent(-1);
        output.write("Bound object:".getBytes());
        indentIncr(+1);
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putBeginError
     */
    public void putBeginError() throws IOException {
        indent(+1);
        output.write("Error".getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putEndError
     */
    public void putEndError() throws IOException {
        indent(-1);
        output.write("/Error".getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putInteger
     */
    public void putInteger(int value) throws IOException {
        indent();
        output.write(Integer.toString(value).getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putInteger
     */
    public void putInteger(int value, int base) throws IOException {
        indent();
        output.write(value);
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putInteger
     */
    public void putInteger(BigInteger value, int base) throws IOException {
        indent();
        if (base != 16)
            base = 10;                 // OM spec says hex or dec, thats all!
        output.write(value.toString(base).getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putInteger
     */
    public void putInteger(BigInteger value) throws IOException {
        putInteger(value, 10);
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putFloat
     */
    public void putFloat(double value, int base) throws IOException {
        indent();
        if (base == 10) {
            output.write(((new Double(value)).toString()).getBytes());
        } else if (base == 16) {
            output.write(("0x" + printLongHexInvertEndianess(
                    java.lang.Double.doubleToLongBits(value))).getBytes());
        } else {
            throw new IOException(
                    "unsuported base for float encoding (" + base + ").");
        }
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putFloat
     */
    public void putFloat(double value) throws IOException {
        putFloat(value, 10);
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putString
     */
    public void putString(String value) throws IOException {
        indent();
        output.write(("\"" + value + "\"").getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putVariable
     */
    public void putVariable(String name) throws IOException {
        indent();
        output.write(name.getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putByteArray
     */
    public void putByteArray(byte[] value) throws IOException {
        int i, LSNib, MSNib, l = value.length;
        String s;

        indent();
        output.write("Bytes:[".getBytes());
        for (i = 0; i < l; i++) {
            MSNib = (value[i] & 0xf0) >> 4;
            LSNib = (value[i] & 0x0f);
            if (MSNib < 10)
                s = String.valueOf('0' + MSNib);
            else
                s = String.valueOf('a' + MSNib - 10);
            if (LSNib < 10)
                s = String.valueOf('0' + LSNib);
            else
                s = String.valueOf('a' + LSNib - 10);
            output.write(s.getBytes());
        }
        output.write((byte) ']');

    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putSymbol
     */
    public void putSymbol(Symbol symbol) throws IOException {
        indent();
        output.write((symbol.getCD() + "." + symbol.getName()).getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putComment
     */
    public void putComment(String comment) throws IOException {
        indent();
        output.write(("//" + comment).getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putPInstruction
     */
    public void putPInstruction(String pi) throws IOException {
        indent();
        output.write(("Processing Instruction: " + pi).getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#endPrint
     */
    public void endPrint() throws IOException {
        output.write((byte) '\n');
        output.flush();
    }

}
